<?php

namespace App\Services;

use Illuminate\Validation\Factory as Validator;
use App\Repositories\RepositoryInterface;
use App\Services\URLService;
use App\Models\Bookmark;
use App\Exceptions\Bookmark\UpdateBookmarkErrorException;
use App\Exceptions\Bookmark\DeleteBookmarkErrorException;
use App\Exceptions\Bookmark\BookmarkValidationErrorException;

/**
 * A service for bookmarks.
 */
class BookmarkService
{
    /**
     * A repository for bookmarks.
     *
     * @var RepositoryInterface
     */
    private $repository;

    /**
     * A validator implementation.
     *
     * @var Validator
     */
    private $validator;

    /**
     * An Http Client
     * @var URLService
     */
    private $URLService;

    /**
     *  Validation rules.
     *
     * @var array
     */
    private $updateRules = [
        'title' => ['sometimes', 'required', 'string'],
        'description' => ['sometimes', 'required', 'string'],
        'url' => ['sometimes', 'required', 'url'],
    ];

    /**
     *  Validation rules.
     *
     * @var array
     */
    private $rules = [
        'url' => ['required', 'url'],
    ];

    /**
     * Service constructor.
     *
     * @param RepositoryInterface $bookmarks  Repository Bookmarks repository.
     * @param Validator           $validator  Input validator.
     * @param URLService              $httpClient HTTP Client
     */
    public function __construct(
        RepositoryInterface $bookmarksRepository,
        Validator $validator,
        URLService $URLService
    ) {
        $this->repository = $bookmarksRepository;
        $this->validator = $validator;
        $this->URLService = $URLService;
    }

    private function loadInfoFromURL(string $url): array
    {
        $this->URLService->setURL($url);

        return [
            'title' => $this->URLService->getTitle(),
            'description' => $this->URLService->getDescription(),
            'url' => $this->URLService->getURL() ?: $url,
        ];
    }

    /**
     * Create a new Bookmark.
     *
     * @param  array $attributes Bookmarks attributes.
     * @return Bookmark
     * @throws BookmarkValidationErrorException
     */
    public function create(array $attributes): Bookmark
    {

        $this->validate($attributes, $this->rules);

        if (empty($attributes['title']) && empty($attributes['description'])) {
            $attributes = $this->loadInfoFromURL($attributes['url']);
        }

        $bookmark = $this->repository->create($attributes);

        return $bookmark;
    }

    /**
     * Update an existing Bookmark.
     *
     * @param  array $attributes Bookmark attributes.
     * @param  int   $id         Identifier for an existing Bookmark.
     * @return Bookmark
     * @throws BookmarkValidationErrorException
     * @throws UpdateBookmarkErrorException
     */
    public function update(array $attributes, int $id): Bookmark
    {
        $this->validate($attributes, $this->updateRules);

        try {
            $this->repository->update($attributes, $id);
        } catch (\Exception $e) {
            throw new UpdateBookmarkErrorException("Can't update bookmark", 400);
        }

        $bookmark = $this->repository->find($id);

        return $bookmark;
    }

    /**
     * Show all existing Bookmarks.
     *
     * @return array
     */
    public function all()
    {
        $bookmarks = $this->repository->all();
        return $bookmarks->toArray();
    }

    /**
     * Show a Bookmark.
     *
     * @param  int $id Bookmark identifier
     * @return Bookmark
     */
    public function find(int $id): Bookmark
    {
        $bookmark = $this->repository->find($id);
        return $bookmark;
    }

    /**
     * Delete a Bookmark.
     *
     * @param  int $id Bookmark identifier.
     * @return bool
     * @throws DeleteBookmarkErrorException
     */
    public function delete(int $id): bool
    {
        $isDeleted = $this->repository->delete($id);

        if (null === $isDeleted) {
            throw new DeleteBookmarkErrorException("Bookmark doesn't exists.", 400);
        }

        return $isDeleted;
    }

    /**
     * Bookmark validation.
     *
     * @param  array $attributes
     * @param  array $rules
     * @throws BookmarkValidationErrorException
     */
    public function validate($attributes, $rules): void
    {
        $validator = $this->validator->make($attributes, $rules);

        if ($validator->fails()) {
            $errorMessage = $this->getValidationMessage($validator->errors()->toArray());
            throw new BookmarkValidationErrorException($errorMessage, 400);
        }
    }

    /**
     * Return a string of validation errors.
     *
     * @param  array $errors
     * @return string
     */
    private function getValidationMessage(array $errors): string
    {
        $result = [];
        foreach ($errors as $error) {
            $result[] = $error[0];
        }
        return implode($result, ' ');
    }
}
